package team.knowers.qzhserver.user.user.service.impl;

import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import team.knowers.qzhserver.common.exception.Asserts;
import team.knowers.qzhserver.common.utils.JwtTokenUtil;
import team.knowers.qzhserver.user.user.entity.dao.UserDO;
import team.knowers.qzhserver.user.user.entity.dto.UserRegisterParamDTO;
import team.knowers.qzhserver.user.user.entity.dto.UserUserDetails;
import team.knowers.qzhserver.user.user.entity.dto.UserUpdateDTO;
import team.knowers.qzhserver.user.user.mapper.UserMapper;
import team.knowers.qzhserver.user.user.service.UserCacheService;
import team.knowers.qzhserver.user.user.service.UserService;
import team.knowers.qzhserver.user.userData.entity.dao.UserDataDO;
import team.knowers.qzhserver.user.userData.mapper.UserDataMapper;


import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author xtg
 * @date 2024/2/11 21:02
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, UserDO> implements UserService {
    private static final Logger LOGGER = LoggerFactory.getLogger(UserServiceImpl.class);
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private UserDataMapper userDataMapper;
    @Autowired
    private RedissonClient redissonClient;
    @Qualifier("bloomFilterUserId")
    @Autowired
    private RBloomFilter<String> bloomFilterUsername;
    @Qualifier("bloomFilterUsername")
    @Autowired
    private RBloomFilter<String> bloomFilterUserId;

    /**
     * 通过用户名和密码登录
     *
     * @param username 用户名
     * @param password 密码
     * @return 返回token
     */
    @Override
    public String login(String username, String password) {
        String token = null;
        //密码需要客户端加密后传递
        try {
            UserUserDetails userDetails = loadUserByUsername(username);
            if (!passwordEncoder.matches(password, userDetails.getPassword())) {
                Asserts.fail("用户名或密码错误");
            }
            if (!userDetails.isEnabled()) {
                Asserts.fail("账号已被禁用");
            }
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null);
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            token = jwtTokenUtil.generateToken(userDetails);
        } catch (AuthenticationException e) {
            LOGGER.warn("登录异常:{}", e.getMessage());
        }
        return token;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public UserDO register(UserRegisterParamDTO userParamDTO) throws Exception {
        UserDO user = new UserDO();
        user.setUsername(userParamDTO.getUsername());
        user.setPassword(userParamDTO.getPassword());
        user.setTelephone(userParamDTO.getUsername());
        System.out.println(user);
        user.setGmtCreate(new Date());
        user.setGmtModified(new Date());
        user.setRole(1);
        //查询是否有相同用户名的用户
        if(getUserByUsernameWithLock(user.getUsername())!=null){
            return null;
        }
        String encodePassword = passwordEncoder.encode(user.getPassword());
        user.setPassword(encodePassword);
        userMapper.insert(user);
        UserDataDO userDataDO = registerUserData(user);
        if(userDataDO==null||user.getId()==null){
            throw new Exception("注册用户失败");
        }
        bloomFilterUserId.add(user.getId());
        bloomFilterUsername.add(user.getUsername());
        getCacheService().setId(user.getUsername(),user.getId());
        getCacheService().setUser(user);
        return user;
    }

    @Override
    public UserDataDO registerUserData(UserDO userDO){
        UserDataDO userDataDO = new UserDataDO();
        userDataDO.setId(userDO.getId());
        Date date = new Date();
        userDataDO.setGmtCreate(date);
        userDataDO.setGmtModified(date);
        userDataDO.setUsername(userDO.getUsername());
        userDataMapper.insert(userDataDO);
        return userDataDO;
    }

    @Override
    public UserUserDetails loadUserByUsername(String username) {
        UserDO user = getUserByUsernameWithLock(username);
        if (user != null) {
            return new UserUserDetails(user);
        }
        throw new UsernameNotFoundException("用户名或密码错误");
    }

    @Override
    public UserCacheService getCacheService() {
        return SpringUtil.getBean(UserCacheService.class);
    }

    @Override
    public UserUpdateDTO getUserUpdateDTOById(String id) {
        UserDO user = getUserById(id);
        UserUpdateDTO userUpdateDTO = new UserUpdateDTO();
        if (user != null) {
            userUpdateDTO.setUsername(user.getUsername());
            userUpdateDTO.setNickname(user.getNickName());
            userUpdateDTO.setAvatar(user.getAvatar());
            userUpdateDTO.setTelephone(user.getTelephone());
            userUpdateDTO.setGmtModified(user.getGmtModified());
            return userUpdateDTO;
        }
        return null;
    }

    @Override
    public boolean editUserEditable (String id,UserUpdateDTO userUpdateDTO){
        UserDO user = getUserById(id);
        if (user != null) {
            //更新数据，然后将数据库中数据存入缓存中
            // TODO 判空，如果前端没写这里就要写
            user.setAvatar(userUpdateDTO.getAvatar());
            user.setNickName(userUpdateDTO.getNickname());
            user.setTelephone(userUpdateDTO.getTelephone());
            user.setGmtModified(new Date());
            getCacheService().setUser(user);
            userMapper.update(user,new QueryWrapper<UserDO>().eq("id", id));
            return true;
        }
        return false;
    }
    @Override
    public int setStatusUnable(String id) {
        UserDO userDO = getUserById(id);
        if (userDO == null)
            return 0;
        userMapper.updateStatus(id, 0);

        userDO.setRole(0);
        UserDO userInCache = getCacheService().getUser(userDO.getId());
        if (userInCache != null) {
            getCacheService().setUser(userDO);
        }
        return 1;
    }

    @Override
    public int setStatusAble(String id) {
        UserDO userDO = getUserById(id);
        if(userDO==null)
            return 0;
        userMapper.updateStatus(id, 1);
        userDO.setRole(1);
        UserDO userInCache = getCacheService().getUser(userDO.getId());
        if (userInCache != null) {
            getCacheService().setUser(userDO);
        }
        return 1;
    }

    @Override
    public int userStatus(String id) {
        UserDO userDO = getUserById(id);
        if(userDO==null){
            return -1;
        }
        return userDO.getRole();
    }

    /*
     * 缓存穿透预案
     * 对于用户这种几乎不用删除的数据类型，可以使用布隆过滤器
       1.先查缓存，缓存不存在？查布隆过滤器，存在就返回结果
       2.查布隆过滤器，布隆过滤器不存在？返回空结果。存在？查空值缓存
       3.查空值缓存，空值缓存存在？返回空结果。不存在？加分布式锁查数据库
       4.查数据库之前，双重判定空值缓存是否为空，缓存里是否存在。然后再查数据库。
       5.数据库存在？加到缓存里。不存在？加到空值缓存。
     */

    @Override
    public String getIdByUsername(String username) {
        //先从缓存获取
        String id = getCacheService().getId(username);
        if(id == null){
            //当缓存中查不到时，查找布隆过滤器
            if(!bloomFilterUsername.contains(username)){
                //布隆过滤器中不存在则一定不存在
                return null;
            }
        }
        if(Objects.equals(id, "")){
            //当空值缓存存在时，返回空结果并更新空值缓存
            getCacheService().setId(username,"");
            return null;
        }else {
            //空值缓存不存在时，先判断缓存是否为空
            if(id==null){
                id = userMapper.selectOne(new QueryWrapper<UserDO>().eq("username", username)).getId();
                if(id ==null){
                    //数据库中也不存在则加到空值缓存
                    getCacheService().setId(username,"");
                    return null;
                }
            }
        }
        //查找到则更新缓存并返回结果
        getCacheService().setId(username,id);
        return id;
    }
    @Override
    public String getUsernameById(String id){
        UserDO userDO=getUserById(id);
        if(userDO==null){
            return null;
        }
        return userDO.getUsername();
    }
    @Override
    public UserDO getUserByUsername(String username) {
        String id = getIdByUsername(username);
        if(id == null){
            return null;
        }
        UserDO userDO = getCacheService().getUser(id);
        if(userDO == null){
            //在根据用户名查询id的过程中已经使用了布隆过滤器，因此当查找到的id不为null时，一定具有对应的用户对象
            userDO = userMapper.selectById(id);
        }
        //查到了则更新缓存并返回对象
        getCacheService().setId(username,userDO.getId());
        getCacheService().setUser(userDO);
        return userDO;
    }
    @Override
    public UserDO getUserById(String id){
        //先从缓存获取
        UserDO userDO = getCacheService().getUser(id);
        //缓存中获取不到时，查布隆过滤器
        if(userDO==null){
            if(!bloomFilterUserId.contains(id)){
                //布隆过滤器中不存在则一定不存在
                return null;
            }
        }
        if (userDO != null && userDO.getId() == null) {
            //当空值缓存存在时，返回空结果并更新空值缓存
            getCacheService().setVoidUser(id);
            return null;
        }else{
            //当空值缓存不存在时，先判空
            if(userDO==null){
                userDO = userMapper.selectById(id);
                if(userDO==null){
                    //数据库不存在则添加空值缓存并返回null
                    getCacheService().setVoidUser(id);
                    return null;
                }
            }
        }
        //更新缓存并返回对象
        getCacheService().setUser(userDO);
        getCacheService().setId(userDO.getUsername(),id);
        return userDO;
    }
    private UserDO getUserByIdWithLock(String id){
        //先从缓存获取
        UserDO userDO = getCacheService().getUser(id);
        //缓存中获取不到时，查布隆过滤器
        if(userDO==null){
            if(!bloomFilterUserId.contains(id)){
                //布隆过滤器中不存在则一定不存在
                return null;
            }
        }
        if (userDO != null && userDO.getId() == null) {
            //当空值缓存存在时，返回空结果并更新空值缓存
            getCacheService().setVoidUser(id);
            return null;
        }else{
            //当空值缓存不存在时，先判空
            if(userDO==null){
                //当空值缓存不存在时，加分布式锁去数据库查询
                RLock rLock = redissonClient.getLock("userLock");
                rLock.lock(10,TimeUnit.SECONDS);
                userDO = userMapper.selectById(id);
                rLock.unlock();
                if(userDO==null){
                    //数据库不存在则添加空值缓存并返回null
                    getCacheService().setVoidUser(id);
                    return null;
                }
            }
        }
        //更新缓存并返回对象
        getCacheService().setUser(userDO);
        getCacheService().setId(userDO.getUsername(),id);
        return userDO;
    }
    private UserDO getUserByUsernameWithLock(String username) {
        String id = getIdByUsername(username);
        if(id == null){
            return null;
        }
        UserDO userDO = getCacheService().getUser(id);
        if(userDO == null){
            //在根据用户名查询id的过程中已经使用了布隆过滤器，因此当查找到的id不为null时，一定具有对应的用户对象
            RLock rLock = redissonClient.getLock("userLock");
            rLock.lock(10,TimeUnit.SECONDS);
            userDO = userMapper.selectById(id);
            rLock.unlock();
        }
        //查到了则更新缓存并返回对象
        getCacheService().setId(username,userDO.getId());
        getCacheService().setUser(userDO);
        return userDO;
    }
    private String getIdByUsernameWithLock(String username) {
        //先从缓存获取
        String id = getCacheService().getId(username);
        if(id == null){
            //当缓存中查不到时，查找布隆过滤器
            if(!bloomFilterUsername.contains(username)){
                //布隆过滤器中不存在则一定不存在
                return null;
            }
        }
        if(Objects.equals(id, "")){
            //当空值缓存存在时，返回空结果并更新空值缓存
            getCacheService().setId(username,"");
            return null;
        }else {
            //空值缓存不存在时，先判断缓存是否为空
            if(id==null){
                //空值缓存不存在，则加分布式锁查询数据库
                RLock rLock = redissonClient.getLock("userLock");
                rLock.lock(10,TimeUnit.SECONDS);
                id = userMapper.selectOne(new QueryWrapper<UserDO>().eq("username", username)).getId();
                rLock.unlock();
                if(id ==null){
                    //数据库中也不存在则加到空值缓存
                    getCacheService().setId(username,"");
                    return null;
                }
            }
        }
        //查找到则更新缓存并返回结果
        getCacheService().setId(username,id);
        return id;
    }
}

